grammar UpdateQuery;

@header {
package ro.one.gaeql.parser;

import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import ro.one.gaeql.model.*;
}

@lexer::header { 
package ro.one.gaeql.parser;
}

@lexer::members {
  /*@Override
  public void reportError(RecognitionException e) {
    throw new IllegalArgumentException(e);
  }*/
}

@members { 
    List<UpdateQuery> queries = new ArrayList<UpdateQuery>();
    Map<String, Kind> aliases = new HashMap<String, Kind>();
    Map<String, Kind> kinds = new HashMap<String, Kind>();
    
    public List<UpdateQuery> getQueries() {
    	return queries;
    }
}

query returns [UpdateQuery updateQuery]
	@init {
		List<Using> usings = new ArrayList<Using>();
		List<Assignment> assignments = new ArrayList<Assignment>();
	}
	:	'UPDATE' kind 'SET' a1=assign { assignments.add(a1); } (',' a2=assign { assignments.add(a2); })* ('WHERE' where)? (u=using { usings.add(u); })* {
		$updateQuery = new UpdateQuery($kind.k, assignments, $where.w, usings, aliases);
	};

using returns [Using u]
	:	'USING' kind 'WHERE' where {
		$u = new Using($kind.k, $where.w);
	};

where returns [Where w]	
	:	condition { $w = new Where($condition.cond, null, null); }
	| 	condition 'AND' w1=where { $w = new Where($condition.cond, LogicalOperator.And, $w1.w); };

condition returns [Condition cond]
	:	'__key__' '=' expr { $cond = new Condition(null, null, $expr.exp); }
	|	'__key__' '=' property { $cond = new Condition(null, $property.prop); }
	|	property '=' expr { $cond = new Condition($property.prop, Operator.Equals, $expr.exp); }
	|	property '<' expr { $cond = new Condition($property.prop, Operator.LessThan, $expr.exp); }
	|	property '<=' expr { $cond = new Condition($property.prop, Operator.LessThanOrEqual, $expr.exp); }
	|	property '>' expr { $cond = new Condition($property.prop, Operator.GreaterThan, $expr.exp); }
	|	property '>=' expr { $cond = new Condition($property.prop, Operator.GreaterThanOrEqual, $expr.exp); }
	|	p1=property '=' p2=property { $cond = new Condition($p1.prop, $p2.prop); };

kind returns [Kind k]
	:	kindName=ID (alias=ID) ?{ 
		if (aliases.containsKey($alias.text)) {
		   throw new RuntimeException("Alias '" + $alias.text + "' is duplicated");
		}
		else if (kinds.containsKey($kindName.text)) {
		   throw new RuntimeException("Kind '" + $kindName.text + "' is duplicated");
		}
		else {
 		   $k = new Kind($kindName.text); 
 		   
 		   if ($alias.text != null) {
                      aliases.put($alias.text, $k);
                   }
                   
                   kinds.put($kindName.text, $k);
		}
	};

assign returns [Assignment assig]
	: 	property '=' assignExpr { 
		$assig = new Assignment($property.prop, $assignExpr.ex);
	};

assignExpr returns [AssignExpression ex]
	:	e1=atom '+' e2=assignExpr { 
			System.out.println($e2.ex.toString());
			$ex = new AssignExpression($e1.value, AssignExpressionOperator.Plus, $e2.ex); 
		}
	|	e1=atom '-' e2=assignExpr { $ex = new AssignExpression($e1.value, AssignExpressionOperator.Minus, $e2.ex); }
	|	e1=atom 'CONCAT' e2=assignExpr { $ex = new AssignExpression($e1.value, AssignExpressionOperator.Concat, $e2.ex); }
	| 	'(' a=assignExpr ')' { $ex = $a.ex; }
//	| 	function
	|	atom { $ex = new AssignExpression($atom.value); }
	;

/*function returns [Function f]
    	:	ID '(' argumentList ')' { $f = new Function($ID.text, $argumentList.args); }
    	;*/

atom returns [Evaluable value]
    :   INT { $value = new Constant(new Integer($INT.text)); }
    | 	STRING { $value = new Constant(StringUtil.trimQuotes($STRING.text)); }
    |	property { $value = $property.prop; }
    ;

argumentList returns [List<Evaluable> args] 
	@init {
		$args = new ArrayList<Evaluable>();
	}
	:	(a1=assignExpr { $args.add(a1); } (',' a2=assignExpr { $args.add(a2); })*)?;

property returns [Property prop]
	:	(alias=ID '.')? propertyName=ID{ 		
		$prop = new Property($alias.text, $propertyName.text); 
	};

expr returns [Object exp]
	:	STRING { $exp = StringUtil.trimQuotes($STRING.text); }
	|	INT { $exp = new Integer($INT.text); }
	|	FLOAT { $exp = new Float($FLOAT.text); };


ID  :	('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*
    ;

INT :	'0'..'9'+
    ;

FLOAT
    :   ('0'..'9')+ '.' ('0'..'9')* EXPONENT?
    |   '.' ('0'..'9')+ EXPONENT?
    |   ('0'..'9')+ EXPONENT
    ;

STRING
    :  '"' ( ESC_SEQ | ~('\\'|'"') )* '"'
    ;

fragment
EXPONENT : ('e'|'E') ('+'|'-')? ('0'..'9')+ ;

fragment
HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ;

fragment
ESC_SEQ
    :   '\\' ('b'|'t'|'n'|'f'|'r'|'\"'|'\''|'\\')
    |   UNICODE_ESC
    |   OCTAL_ESC
    ;

fragment
OCTAL_ESC
    :   '\\' ('0'..'3') ('0'..'7') ('0'..'7')
    |   '\\' ('0'..'7') ('0'..'7')
    |   '\\' ('0'..'7')
    ;

fragment
UNICODE_ESC
    :   '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
    ;
